package utils.sl651;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * SL651协议十六进制编码报文解析核心类
 * 
 * 实现SL651-2014标准的十六进制编码上行报文解析功能
 */
public class SL651HexMessageParser {
    
    /**
     * 解析十六进制编码上行报文
     * 
     * @param hexMessage 十六进制编码的报文字符串
     * @return 解析后的报文对象
     */
    public static SL651Message parseUplinkMessage(String hexMessage) {
        SL651Message message = new SL651Message();
        try {
            // 解析报文头部
            parseMessageHeader(hexMessage, message);
            
            // 二次验证CRC校验码
            boolean crcValid = SL651CrcUtil.verifyHexMessageCrc(hexMessage);
            message.setCrcValid(crcValid);
            
            if (!crcValid) {
                message.setErrorMessage("CRC校验失败");
                return message;
            }
            
            // 解析数据部分
            parseMessageData(hexMessage, message);
            
            message.setValid(true);
            
        } catch (Exception e) {
            message.setErrorMessage("解析异常: " + e.getMessage());
            message.setValid(false);
        }
        
        return message;
    }
    
    /**
     * 解析报文头部信息
     */
    private static void parseMessageHeader(String message, SL651Message messageObj) {
        int pos = 4; // 跳过起始符7E7E
        
        // 解析中心站地址（1字节HEX）
        String centerAddr = message.substring(pos, pos + 2);
        messageObj.setCenterAddress(centerAddr);
        pos += 2;
        
        // 解析遥测站地址（5字节HEX）
        String stationAddr = message.substring(pos, pos + 10);
        messageObj.setStationAddress(stationAddr);
        pos += 10;
        
        // 解析密码（2字节HEX）
        String password = message.substring(pos, pos + 4);
        messageObj.setPassword(password);
        pos += 4;
        
        // 解析功能码（1字节HEX）
        String functionCode = message.substring(pos, pos + 2);
        messageObj.setFunctionCode(functionCode);
        pos += 2;
        
        // 解析上行标识和长度（2字节）
        String flagAndLength = message.substring(pos, pos + 4);
        messageObj.setFlagAndLength(flagAndLength);
        pos += 4;
        
        // 验证数据起始符STX（02）
        String stx = message.substring(pos, pos + 2);
        if (!"02".equals(stx)) {
            throw new IllegalArgumentException("数据起始符错误，应为02，实际为: " + stx);
        }
        
        // 提取CRC校验码（最后2字节HEX）
        String crcCode = message.substring(message.length() - 4);
        messageObj.setCrcCode(crcCode);
    }
    
    /**
     * 解析报文数据部分
     */
    private static void parseMessageData(String message, SL651Message messageObj) {
        // 头部结束位置：帧起始符(4) + 中心站地址(2) + 遥测站地址(10) + 密码(4) + 功能码(2) + 标识符长度(4) + STX(2)
        int headerEndPos = 4 + 2 + 10 + 4 + 2 + 4 + 2; // = 28
        
        // 计算数据起始位置（头部之后）
        int dataStartPos = headerEndPos;
        
        // 计算数据结束位置（去掉ETX和CRC）
        int dataEndPos = message.length() - 6; // 减去ETX(2字节) + CRC(4字节)
        
        if (dataStartPos >= dataEndPos) {
            // 无数据内容（如链路维持报文）
            return;
        }
        // 提取原始数据部分
        String rawData = message.substring(dataStartPos, dataEndPos);
        messageObj.setRawData(rawData);
        
        // 解析流水号（2字节HEX）- 这部分移到数据解析中
        if (rawData.length() >= 4) {
            String serialNumber = rawData.substring(0, 4);
            messageObj.setSerialNumber(serialNumber);
        }
        
        // 解析发报时间（6字节BCD码）- 这部分移到数据解析中
        if (rawData.length() >= 16) { // 4(流水号) + 12(发报时间)
            String reportTime = rawData.substring(4, 16);
            messageObj.setReportTime(SL651CommonUtil.parseBcdTime(reportTime));
        }
        
        // 如果数据只包含流水号和时间，则不需要进一步解析
        if (rawData.length() <= 16) {
            return;
        }
        // 实际数据内容（去掉流水号和发报时间）
        String dataContent = rawData.substring(16);
        // 根据功能码选择解析方式
        String functionCode = messageObj.getFunctionCode();
        
        if (functionCode == null) {
            return;
        }
        switch (functionCode) {
            case SL651Protocol.FUNC_LINK_MAINTAIN:
                // 链路维持报文无数据内容
                break;
            case SL651Protocol.FUNC_TEST_REPORT:
                // 测试报文处理，根据SL651-2014协议表28结构解析具体元素数据
                parseTestReportData(dataContent, messageObj);
                break;
            case SL651Protocol.FUNC_TIMER_REPORT:
                // 定时报文处理，根据SL651-2014协议表28结构解析具体元素数据
                parseTestReportData(dataContent, messageObj);
                break;
            case SL651Protocol.FUNC_ADD_REPORT:
                // 加报报文处理，根据SL651-2014协议表28结构解析具体元素数据
                parseTestReportData(dataContent, messageObj);
                break;
            case SL651Protocol.FUNC_HOUR_REPORT:
                // 小时报文处理，根据SL651-2014协议表28结构解析具体元素数据
                parseTestReportData(dataContent, messageObj);
                break;
            default:
                messageObj.addDataElement("RAW_DATA", dataContent);
                break;
        }
    }
    
    /**
     * 解析测试报文数据（功能码30）
     * 根据SL651-2014协议表28"遥测站测试报的上行报文正文结构"进行解析
     * 
     * 测试报文结构：
     * 1. 流水号：2字节HEX码
     * 2. 发报时间：6字节BCD码，格式为yyMMddHHmmss
     * 3. 遥测站地址标识符：F1F1
     * 4. 遥测站地址：5字节
     * 5. 遥测站分类码：1字节
     * 6. 观测时间标识符：F0F0
     * 7. 观测时间：5字节BCD码，格式为yyMMddHH
     * 8. 数据内容：各种要素数据（降水量、水位等）
     * 9. 电压数据：电压值
     * 
     * 常用水文数据元素示例：
     * - 降水量元素示例：
     *   标识符: 2019 (高位字节20表示当前降水量，低位字节19表示数据长度和小数位)
     *   数据: 0005 (实际降水量为5.0mm)
     *   解析: 20190005 表示当前降水量为5.0mm
     *   
     * - 水位元素示例：
     *   标识符: 3923 (高位字节39表示瞬时河道水位，低位字节23表示数据长度和小数位)
     *   数据: 000127 (实际水位为1.27m)
     *   解析: 3923000127 表示瞬时河道水位为1.27m
     *   
     * - 电压元素示例：
     *   标识符: 3812 (高位字节38表示电源电压，低位字节12表示数据长度和小数位)
     *   数据: 1150 (实际电压为11.50V)
     *   解析: 38121150 表示电源电压为11.50V
     *   
     * - 累计降水量示例：
     *   标识符: 2619 (高位字节26表示降水量累计值，低位字节19表示数据长度和小数位)
     *   数据: 000910 (实际累计降水量为9.10mm)
     *   解析: 2619000910 表示降水量累计值为9.10mm
     */
    private static void parseTestReportData(String dataSegment, SL651Message messageObj) {
        try {

            System.out.println("数据段详细解析:");
            int pos = 0;
            int elementIndex = 0;

            while (pos < dataSegment.length()) {
                elementIndex++;
                System.out.print("  元素 " + elementIndex + ": ");

                // 检查是否还有足够的字符来读取标识符（2字节，4个HEX字符）
                if (pos + 4 > dataSegment.length()) {
                    System.out.println("剩余字符不足: " + dataSegment.substring(pos));
                    break;
                }

                // 读取2字节标识符（4个HEX字符）
                String identifier = dataSegment.substring(pos, pos + 4);
                System.out.print(identifier + " ");
                pos += 4;

                // 解析标识符的高位字节和低位字节
                String highByte = identifier.substring(0, 2);
                String lowByte = identifier.substring(2, 4);

                // 根据标识符类型处理
                switch (highByte.toUpperCase()) {
                    case "F1": // 地址标识符  遥测站地址与遥测 站分类码是固定组合
                        System.out.print("地址标识符 ");
                        if (pos + 12 <= dataSegment.length()) {
                            String address = dataSegment.substring(pos, pos + 12);
                            String stationType = dataSegment.substring(pos + 10, pos + 12);
                            messageObj.addDataElement(SL651Protocol.ELEM_STATION_ID, address);
                            messageObj.addDataElement(SL651Protocol.ELEM_STATION_TYPE, stationType);
                            System.out.println("遥测站地址=" + address);
                            pos += 12;
                        } else {
                            System.out.println("数据不足");
                        }
                        break;

                case "F0": // 观测时间标识符 YYMMDDHHmm
                    System.out.print("观测时间标识符 ");
                    if (pos + 10 <= dataSegment.length()) {
                        String time = dataSegment.substring(pos, pos + 10);
                        String timeStr = SL651CommonUtil.parseBcdTime2(time);
                        // 转换为LocalDateTime
                        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                        LocalDateTime dateTime = LocalDateTime.parse(timeStr, formatter);
                        messageObj.addDataElement(SL651Protocol.ELEM_OBSERVE_TIME, timeStr);
                        messageObj.setObserveDateTime(dateTime);
                        System.out.println("观测时间=" + timeStr);
                        pos += 10;
                    } else {
                        System.out.println("数据不足");
                    }
                    break;
                case "20": // 当前降水量标识符
                    System.out.print("当前降水量标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_CURRENT_RAINFALL, "mm");
                    break;
                case "26": // 降水量累计值标识符
                    System.out.print("降水量累计值标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_TOTAL_RAINFALL, "mm");
                    break;
                case "22": // 五分钟降水量标识符
                    System.out.print("五分钟降水量标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.FIVE_MIN_RAINFALL, "mm");
                    break;
                case "3B": // 库闸上水位
                    System.out.print("库闸上水位 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_WATER_LEVEL, "m");
                    break;
                case "38": // 电压标识符
                    System.out.print("电压标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_VOLTAGE, "V");
                    break;
                case "45": // 遥测站状态及报警信息标识符
                    System.out.print("遥测站状态及报警信息标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.STATION_STATUS, "");
                    break;
                case "30": // 瞬时流量标识符
                    System.out.print("瞬时流量标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_FLOW_RATE, "m³/s");
                    break;
                case "28": // 水温标识符
                    System.out.print("水温标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_WATER_TEMP, "℃");
                    break;
                case "29": // 气温标识符
                    System.out.print("气温标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_TEMPERATURE, "℃");
                    break;
                case "2B": // PH值标识符
                    System.out.print("PH值标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_PH, "");
                    break;
                case "2C": // 溶解氧标识符
                    System.out.print("溶解氧标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_DISSOLVED_OXYGEN, "mg/L");
                    break;
                case "2D": // 电导率标识符
                    System.out.print("电导率标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_CONDUCTIVITY, "μS/cm");
                    break;
                case "2E": // 浊度标识符
                    System.out.print("浊度标识符 ");
                    pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.ELEM_TURBIDITY, "NTU");
                    break;
                case "FF": // 设备相关信息标识符
                    System.out.print("设备相关信息标识符 ");
                    switch (lowByte) {
                        case "02": // 设备温度
                            lowByte =  dataSegment.substring(pos, pos + 2);
                            pos += 2;
                            pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.DEVICE_TEMPERATURE, "℃");
                            break;
                        case "03": // 信号强度
                            lowByte =  dataSegment.substring(pos, pos + 2);
                            pos += 2;
                            pos = parseElementData(messageObj,dataSegment, pos, lowByte, SL651Protocol.SIGNAL_STRENGTH, "");
                            break;
                        default:
                            break;
                    }
                    break;
                case "FE": // 自定义测试数据标识符（渗压数据等）
                    System.out.print("自定义测试数据标识符: "+ identifier);
                    // 处理FE标识符的自定义数据元素解析
                    pos = parseCustomElementData(messageObj, dataSegment, pos, lowByte, identifier);
                    break;
                default:
                    // 在default分支中匹配自定义元素标签并解析数据值
                    System.out.print("未知标识符，尝试匹配自定义元素标签 ");
                    // 解析数据值
                    pos = parseCustomElementData(messageObj, dataSegment, pos, lowByte, identifier);
                    break;
            }
        }
        
    } catch (Exception e) {
        e.printStackTrace();
        messageObj.setErrorMessage("测试报文数据解析失败: " + e.getMessage());
    }
}

    /**
     * 解析自定义元素数据
     * 根据自定义元素标签解析数据值
     * 
     * @param messageObj 消息对象
     * @param dataSegment 数据段
     * @param pos 当前位置
     * @param lowByte 低位字节
     * @return 解析后的新位置
     */
    private static int parseCustomElementData(SL651Message messageObj, String dataSegment, int pos, String lowByte, String fullIdentifier) {
        try {
            lowByte =  dataSegment.substring(pos, pos + 2);
            pos += 2;
            // 查找匹配的自定义元素标签
            SL651CustomElementLabel customLabel = SL651CustomElementLabelManager.getLabelByIdentifier(fullIdentifier);
            if (null == customLabel) {
                System.out.println("未找到匹配的自定义元素标签");
                return pos;
            }
            System.out.print("匹配到自定义元素: " + customLabel.getRemark() + " ");
            // 低位字节的高4位表示数据字节数，低4位表示小数点后位数
            int lowByteValue = Integer.parseInt(lowByte, 16);
            int dataBytes = (lowByteValue >> 3) & 0x1F;  // 高5位
            int decimalPlaces = lowByteValue & 0x07;     // 低3位
            System.out.print("(字节数=" + dataBytes + ", 小数位=" + decimalPlaces + ") ");

            if (pos + (dataBytes * 2) <= dataSegment.length()) {
                String valueHex = dataSegment.substring(pos, pos + (dataBytes * 2));

                // 将十六进制数值转换为十进制
                long valueDecimal = parseBcdValue(valueHex);

                // 计算实际数值（除以10的小数位次方）
                double actualValue = valueDecimal / Math.pow(10, decimalPlaces);

                // 格式化输出（保留对应小数位）
                String format = "%." + decimalPlaces + "f%s";
                String format2 = "%." + decimalPlaces + "f";
                System.out.println(customLabel.getElementName() + "=" + String.format(format, actualValue, customLabel.getUnit()));
                messageObj.addDataElement(customLabel.getElementName(), String.format(format2, actualValue));
                // 返回更新后的位置
                return pos + (dataBytes * 2);
            } else {
                System.out.println("数据不足");
                return pos;
            }
        } catch (Exception e) {
            System.out.println("自定义元素解析失败: " + e.getMessage());
            return pos;
        }
    }

    /**
     * 公共方法：解析元素数据
     * @param dataSegment 数据段
     * @param pos 当前位置
     * @param lowByte 低位字节
     * @param elementName 元素名称
     * @param unit 单位
     * @return 解析后的新位置
     */
    private static int parseElementData(SL651Message messageObj, String dataSegment, int pos, String lowByte, String elementName, String unit) {
        try {
            // 低位字节的高4位表示数据字节数，低4位表示小数点后位数
            int lowByteValue = Integer.parseInt(lowByte, 16);
            int dataBytes = (lowByteValue >> 3) & 0x1F;  // 高5位
            int decimalPlaces = lowByteValue & 0x07;     // 低3位
            System.out.print("(字节数=" + dataBytes + ", 小数位=" + decimalPlaces + ") ");

            if (pos + (dataBytes * 2) <= dataSegment.length()) {
                String valueHex = dataSegment.substring(pos, pos + (dataBytes * 2));

                // 将十六进制数值转换为十进制
                long valueDecimal = parseBcdValue(valueHex);

                // 计算实际数值（除以10的小数位次方）
                double actualValue = valueDecimal / Math.pow(10, decimalPlaces);

                // 格式化输出（保留对应小数位）
                String format = "%." + decimalPlaces + "f%s";
                String format2 = "%." + decimalPlaces + "f";
                System.out.println(elementName + "=" + String.format(format, actualValue, unit));
                messageObj.addDataElement(elementName, String.format(format2, actualValue));
                // 返回更新后的位置
                return pos + (dataBytes * 2);
            } else {
                System.out.println("数据不足");
                return pos;
            }
        } catch (Exception e) {
            System.out.println("解析失败: " + e.getMessage());
            return pos;
        }
    }

    /**
     * 将BCD编码的十六进制字符串解析为十进制数值
     * BCD编码中每个十六进制字符代表一个十进制数字
     * 例如："1234" 表示十进制数 1234
     */
    private static long parseBcdValue(String hexValue) {
        long result = 0;
        for (int i = 0; i < hexValue.length(); i++) {
            char c = hexValue.charAt(i);
            int digit;
            if (c >= '0' && c <= '9') {
                digit = c - '0';
            } else if (c >= 'A' && c <= 'F') {
                digit = c - 'A' + 10;
            } else {
                throw new NumberFormatException("Invalid hex character: " + c);
            }
            result = result * 10 + digit;
        }
        return result;
    }

    /**
     * 解析DRP（5分钟间隔雨量）数据
     */
    private static void parseDrpData(String drpHex, SL651Message messageObj) {
        try {
            // DRP数据：24个十六进制字符，每2个字符表示5分钟雨量
            if (drpHex == null || drpHex.length() != 24) {
                messageObj.addDataElement("DRP_PARSE_ERROR", "DRP数据长度不正确，期望24字符，实际" + (drpHex != null ? drpHex.length() : 0));
                return;
            }

            String[] rainfallData = new String[12]; // 1小时=12个5分钟
            double totalRainfall = 0;
            int validCount = 0;

            for (int i = 0; i < 12; i++) {
                String hexValue = drpHex.substring(i * 2, i * 2 + 2);
                if ("FF".equals(hexValue)) {
                    rainfallData[i] = "无效数据";
                } else {
                    try {
                        int value = Integer.parseInt(hexValue, 16);
                        double rainfall = value * 0.1; // 假设精度0.1mm
                        rainfallData[i] = String.format("%.1fmm", rainfall);
                        totalRainfall += rainfall;
                        validCount++;
                    } catch (NumberFormatException e) {
                        rainfallData[i] = "格式错误";
                    }
                }
            }

            // 存储解析后的雨量数据
            messageObj.addDataElement("DRP_PARSED", String.join(",", rainfallData));
            messageObj.addDataElement("DRP_TOTAL_RAINFALL", String.format("%.1fmm", totalRainfall));
            messageObj.addDataElement("DRP_VALID_COUNT", String.valueOf(validCount));
            messageObj.addDataElement("DRP_VALID_RATE", String.format("%.1f%%", (validCount * 100.0 / 12)));

        } catch (Exception e) {
            messageObj.addDataElement("DRP_PARSE_ERROR", "解析异常: " + e.getMessage());
        }
    }

    /**
     * 解析DRZ1（5分钟间隔水位）数据
     */
    private static void parseDrzData(String drzHex, SL651Message messageObj) {
        try {
            // DRZ1数据：48个十六进制字符，每4个字符表示5分钟水位
            if (drzHex == null || drzHex.length() != 48) {
                messageObj.addDataElement("DRZ1_PARSE_ERROR", "DRZ1数据长度不正确，期望48字符，实际" + (drzHex != null ? drzHex.length() : 0));
                return;
            }

            String[] waterLevelData = new String[12]; // 1小时=12个5分钟
            double maxLevel = Double.MIN_VALUE;
            double minLevel = Double.MAX_VALUE;
            double totalLevel = 0;
            int validCount = 0;

            for (int i = 0; i < 12; i++) {
                String hexValue = drzHex.substring(i * 4, i * 4 + 4);
                if ("FFFF".equals(hexValue)) {
                    waterLevelData[i] = "无效数据";
                } else {
                    try {
                        int value = Integer.parseInt(hexValue, 16);
                        double level = value * 0.01; // 假设精度0.01m
                        waterLevelData[i] = String.format("%.2fm", level);

                        maxLevel = Math.max(maxLevel, level);
                        minLevel = Math.min(minLevel, level);
                        totalLevel += level;
                        validCount++;
                    } catch (NumberFormatException e) {
                        waterLevelData[i] = "格式错误";
                    }
                }
            }

            // 存储解析后的水位数据
            messageObj.addDataElement("DRZ1_PARSED", String.join(",", waterLevelData));

            if (validCount > 0) {
                messageObj.addDataElement("DRZ1_MAX_LEVEL", String.format("%.2fm", maxLevel));
                messageObj.addDataElement("DRZ1_MIN_LEVEL", String.format("%.2fm", minLevel));
                messageObj.addDataElement("DRZ1_AVG_LEVEL", String.format("%.2fm", totalLevel / validCount));
                messageObj.addDataElement("DRZ1_LEVEL_RANGE", String.format("%.2fm", maxLevel - minLevel));
                messageObj.addDataElement("DRZ1_VALID_COUNT", String.valueOf(validCount));
                messageObj.addDataElement("DRZ1_VALID_RATE", String.format("%.1f%%", (validCount * 100.0 / 12)));
            }

        } catch (Exception e) {
            messageObj.addDataElement("DRZ1_PARSE_ERROR", "解析异常: " + e.getMessage());
        }
    }
}